package com.aliang.shopping.log.aspect;


import cn.hutool.core.util.StrUtil;
import com.aliang.shopping.contact.SqlColumnConstant;
import com.aliang.shopping.log.annotation.OperateLog;
import com.aliang.shopping.model.po.OperationLog;
import com.aliang.shopping.security.auth.Payload;
import com.aliang.shopping.security.auth.UserHolder;
import com.aliang.shopping.service.OperationLogService;
import com.aliang.shopping.util.RequestUtil;
import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 操作日志记录处理
 */
@Aspect
@Component
@AllArgsConstructor
@Slf4j
public class OperateLogAspect {
    private final RequestUtil requestUtil;
    private final OperationLogService operationLogService;

    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "@annotation(operateLog)")
    public void doAfterReturning(JoinPoint joinPoint, OperateLog operateLog) {
        handleOperateLog(joinPoint, operateLog, null);
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint 切点
     * @param e         异常
     */
    @AfterThrowing(value = "@annotation(operateLog)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, OperateLog operateLog, Exception e) {
        handleOperateLog(joinPoint, operateLog, e);
    }

    /**
     * 处理日志
     *
     * @param joinPoint
     * @param operateLog
     * @param e
     */
    protected void handleOperateLog(final JoinPoint joinPoint, OperateLog operateLog, final Exception e) {
        //通过上下文环境，获取请求对象
        RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
        //获取IP地址
        String clientIP = this.requestUtil.getClientIP(request);
        // 获取IP来源
        String ipSource = this.requestUtil.getRegion(clientIP);
//        log.info("请求IP：{}，IP来源：{}", clientIP, ipSource);
        if (StrUtil.isBlank(ipSource)) {
            ipSource = this.requestUtil.getRegion(clientIP);
        }
        //获取UA信息
        RequestUtil.UA ua = this.requestUtil.getUa(request);
        //获取请求参数
        Object[] args = joinPoint.getArgs();
        String arg = Arrays.stream(args)
                .filter(o -> !isFilterObject(o))
                .map(o -> JSON.toJSON(o).toString())
                .collect(Collectors.joining(","));
        OperationLog operationLog = OperationLog.builder()
                .title(operateLog.title()) //模块标题
                .operationType(operateLog.type().getDesc()) // 操作类型
                .requestUrl(request.getRequestURI()) // 请求地址
                .requestMethod(request.getMethod()) // 请求方式
                .requestParam(arg) //请求参数
                .os(ua.getOs()) // 操作系统
                .browser(ua.getBrowser()) // 浏览器
                .operationIp(clientIP)
                .ipRegion(ipSource)
                .build();

        //查看线程是否有登录数据
        Payload payload = UserHolder.get();
        if (Objects.nonNull(payload)) {
            operationLog.setPersonId(payload.getId());
            operationLog.setPersonType(payload.getRole());
        }
        //判断是否成功
        if (Objects.nonNull(e)) {
            operationLog.setErrorMsg(e.getMessage());
            operationLog.setStatus(SqlColumnConstant.STATUS_FAIL);
        }
        //存入数据库
        this.operationLogService.save(operationLog);
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
                || o instanceof BindingResult;
    }
}